package com.noahsloan.nutils.streams;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * A DelimitedInputStream wraps another InputStream and allows reading from that
 * InputStream up to and including a given delimeter (a byte sequence).
 * <p>
 * It is useful for wrapping InputStreams that you want to pass to a Scanner or
 * other buffering reader but that will need to be accessed directly later. i.e.
 * you need a Scanner but aren't willing to give up all the data.
 * <p>
 * Note that if you know exactly how many bytes you want to give to the Scanner
 * (or whatever) you should use a {@link PartialInputStream} as it implements
 * all 3 read methods and will generally be more efficient.
 * <p>
 * Note that this class will <em>never</em> buffer ahead, so it may be
 * desirable to wrap a stream in a {@link BufferedInputStream} before handing it
 * to this class.
 * <p>
 * Regex support will be implemented in another class in a future release,
 * however, it is possible to delimit on multiple tokens by layering multiple
 * <code>DelimitedInputStream</code>s (since a caught delimeter in one layer
 * will result in an EOS all the way up to the top layer)
 * 
 * @author Noah Sloan
 * 
 */
public class DelimitedInputStream extends InputStream {

    private byte[] delimeter; // the delimeter to look for

    private byte[] seen; // the last delimeter.length bytes seen

    private int index; // front of seen

    private InputStream in; // stream to read bytes from

    /**
     * 
     * @param in
     * @param delimeter
     *            the delimeter to halt after
     */
    public DelimitedInputStream(InputStream in, byte[] delimeter) {
        this.in = in;
        this.delimeter = delimeter;
        this.seen = new byte[delimeter.length];
        this.index = 0;
    }

    @Override
    /**
     * Obeys the general contract of read. Returns -1 at end of stream.
     */
    public int read() throws IOException {
        if (index != -1) {
            int len = delimeter.length;
            int i = in.read();
            // record each byte we see
            seen[index] = (byte) i;
            index = (index + 1) % len;

            // see if the last len bytes match the delimeter
            for (int j = 0; j < len; j++) {
                if (delimeter[j] != seen[(index + j) % len]) {
                    return i;
                }
            }
            index = -1;
            return i;
        }
        return -1;
    }

    @Override
    /**
     * Reads up to the delimeter or EOS. In other words, consumes all the bytes
     * up to and including the delimeter. Does not close the underlying stream.
     */
    public void close() throws IOException {
        while (read() != -1)
            ;
    }
}